# WQuake 插件开发文档

WQuake 插件系统的**唯一开发文档**。涵盖插件结构、清单、权限、完整 `wq` API、设置表单、
测站数据接口、主题插件、打包安装，以及内置示例插件说明。

> 内置示例插件位于本目录：`com.wquake.template`（通用模板）、`com.wquake.station-template`
> （测站数据模板）、`com.wquake.lighttheme`（浅色主题）。复制任一目录即可起步。

---

## 1. 架构概览

- 每个插件在一个**沙箱 iframe**（`sandbox="allow-scripts"`）中运行，与宿主**隔离**。
- 插件通过宿主注入的全局对象 **`wq`** 与 WQuake 交互，底层是 `postMessage` RPC 桥。
- 插件能做什么，完全由 `manifest.json` 的 `permissions` 决定；未声明的权限调用会被拒绝。
- 插件入口是一个 **`onLoad(wq)`** 函数；禁用/卸载时触发 `wq.onUnload(cb)` 注册的清理回调。

---

## 2. 目录结构

```
my-plugin/
├── manifest.json         # 必需：元数据 + 权限 + 贡献点
├── index.js              # JS 插件入口（定义 onLoad(wq)）
├── settings.schema.json  # 可选：声明式设置，自动渲染到「插件」设置页
├── theme.css             # 可选：主题插件样式（需 ui:theme 权限 + contributes.styles）
├── assets/               # 可选：音频、图片等资源
└── README.md             # 可选：插件自述
```

---

## 3. manifest.json

```json
{
  "id": "com.author.name",
  "name": "插件名称",
  "version": "1.0.0",
  "author": "作者",
  "description": "一句话描述",
  "type": "js",
  "apiVersion": "1.0",
  "minAppVersion": "3.4.0",
  "entry": "index.js",
  "permissions": ["events:eew", "ui:toast", "settings"],
  "contributes": { "settingsPage": true }
}
```

| 字段 | 说明 |
|------|------|
| `id` | 全局唯一，建议反向域名，如 `com.author.name` |
| `name` / `version` / `author` / `description` | 展示信息，`version` 用语义化版本 |
| `type` | `"js"`（默认）或 `"native"`（暂不支持，架构预留） |
| `apiVersion` | 需要的插件 API 版本，宿主据此判断兼容（当前 `1.0`） |
| `minAppVersion` | 需要的最低 WQuake 版本 |
| `entry` | JS 入口文件名 |
| `permissions` | 声明式权限（见下），启用时授予 |
| `contributes.settingsPage` | 是否在「插件」设置页渲染设置表单 |
| `contributes.styles` | 要注入主文档的 CSS 文件列表（主题插件用） |

---

## 4. 权限清单

| 权限 | 能力 |
|------|------|
| `events:eew` | 订阅地震预警（EEW）实时数据流 |
| `events:earthquake` | 订阅地震情报实时数据流 |
| `events:tsunami` | 订阅海啸实时数据流 |
| `sound` | 播放音频 |
| `ui:toast` | 弹出 Toast 提示 |
| `ui:theme` | 注入 CSS 主题到主文档（配合 `contributes.styles`） |
| `stations` | 注入自定义测站（地图圆点 + 测站栏）；并可读取可视 PGA/PGV（`getMetrics`） |
| `data:eew` | 向宿主注入 EEW 预警数据（走真实处理管线） |
| `data:earthquake` | 向宿主注入地震情报/历史数据 |
| `data:tsunami` | 向宿主注入海啸预警数据 |
| `settings` | 读取本插件设置 |
| `storage` | 插件私有持久化存储 |

---

## 5. 生命周期与入口

```js
function onLoad(wq) {
  // 初始化：订阅事件、读设置、启动定时器等
  const off = wq.events.on('eew', (eew) => { /* ... */ })

  // 注册清理回调：插件被禁用/卸载时触发
  wq.onUnload(() => {
    off()
    // 停止定时器、清空注入的数据等
  })
}

// 兼容两种写法：ESM 风格 export 或直接定义全局 onLoad
if (typeof window !== 'undefined') window.onLoad = onLoad
```

---

## 6. `wq` API 完整参考

> 受 `manifest.permissions` 授权限制。异步方法返回 `Promise`。

### 6.1 基础信息

| API | 说明 |
|-----|------|
| `wq.app` | `{ version, apiVersion }` 宿主版本信息 |
| `wq.plugin` | `{ id, version }` 本插件信息 |

### 6.2 事件订阅 —— `wq.events`

```js
const off = wq.events.on('eew', (payload) => { /* ... */ })
// off() 取消订阅
```

- `type`：`'eew'` | `'earthquake'` | `'tsunami'`（需对应 `events:*` 权限）。
- 返回一个取消订阅函数。

### 6.3 提示 —— `wq.ui`（需 `ui:toast`）

```js
wq.ui.toast('消息内容', 'warning') // type: 'info' | 'success' | 'error' | 'warning'
```

### 6.4 声音 —— `wq.sound`（需 `sound`）

```js
wq.sound.play('assets/ding.mp3')   // 插件目录内相对路径
wq.sound.play('https://.../a.mp3') // 或 http(s)/data URL
```

### 6.5 存储 —— `wq.storage`（需 `storage`）

```js
await wq.storage.set('key', anyJsonValue)
const v = await wq.storage.get('key') // 不存在返回 null
```

插件私有、持久化，按插件 id 隔离。

### 6.6 设置 —— `wq.settings`（需 `settings`）

```js
const all = wq.settings.getAll()          // 同步返回当前设置对象
const x = wq.settings.get('scale')        // 读单项
const off = wq.settings.onChange((values) => { /* 设置变更回调 */ })
```

设置项由 `settings.schema.json` 声明（见第 7 节）。

### 6.7 自定义测站 —— `wq.stations`（需 `stations`）

把「你自己的测站数据」显示到地图圆点 + 侧边测站栏。

```js
wq.stations.setBatch([
  { id: 's1', name: '台站A', lat: 39.9, lng: 116.4, value: 4.2, scale: 'shindo' },
  { id: 's2', name: '台站B', lat: 31.2, lng: 121.5, value: 6,   scale: 'intensity' },
  { id: 's3', name: '台站C', lat: 24.1, lng: 120.7, value: 25,  scale: 'pga' },
  // 台站还可携带原始 PGA/PGV（仅用于「插件测站」面板方框展示，不影响圆点颜色）
  { id: 's4', name: '台站D', lat: 23.0, lng: 120.2, value: 3.1, scale: 'shindo', pga: 42, pgv: 8.5 }
])
wq.stations.clear() // 清空本插件的所有测站
```

**台站字段**

| 字段 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| `name` | string | ✅ | 台站名称（显示在测站栏与地图） |
| `lat` | number | ✅ | 纬度（亦可用 `latitude`） |
| `lng` | number | ✅ | 经度（亦可用 `lon` / `longitude`） |
| `value` | number | ✅ | 地图圆点数值；`scale='pga'` 时填 PGA(gal) |
| `scale` | string | ✅ | 圆点配色依据：`'shindo'` 震度 / `'intensity'` 烈度 / `'pga'` 峰值加速度 |
| `pga` | number | ⬜ | 可选原始 PGA(gal)，显示在面板 PGA 方框（取全部测站最大值，**不**做烈度换算） |
| `pgv` | number | ⬜ | 可选原始 PGV(kine)，显示在面板 PGV 方框（取全部测站最大值，**不**做烈度换算） |
| `id` | string | ⬜ | 稳定标识；同 id 再次提交会平滑更新位置/数值 |

**圆点刻度 (scale)** —— 决定地图圆点与测站栏主值的配色与显示

| scale | 含义 | 数值示例 | 显示 |
|-------|------|---------|------|
| `shindo` | 震度 | `4.2` / `5.1` | `4` / `5-` |
| `intensity` | 烈度 | `6` | `6`（四舍五入整数） |
| `pga` | 峰值加速度(gal) | `25` / `140` | 宿主按 CWA 阈值换算成烈度后着色/显示（`25 gal → 3`） |

配色随 `scale` 自动选择对应体系（震度用震度色，烈度/PGA 用烈度色）。

**PGA / PGV 方框（可视化原始值）**：给测站加上可选的 `pga` / `pgv` 字段，它们会以**原始数值**（不换算成烈度）
显示在「插件测站」面板的 PGA/PGV 圆角方框里，取本插件全部测站的最大值，配色按 gal/kine 阈值（与 NIED/TREM 一致）。
不传则不显示对应方框。单位：PGA=gal，PGV=kine(cm/s)。

> **`setBatch` 是「整批替换」语义**：每次调用请提交本插件当前应显示的**完整**列表，
> 漏传的台站会被移除。各插件互不影响（宿主按插件 id 命名空间隔离）。
> 插件被禁用/卸载时宿主会自动清空本插件测站，建议同时在 `wq.onUnload` 里 `wq.stations.clear()`。

**读取可视 PGA/PGV —— `wq.stations.getMetrics()`**

读取当前**界面震度栏正在显示**的 PGA/PGV 峰值（来自 NIED 强震观测网、台湾 TREM、台湾 P-Alert）。
数值受各自「显示 PGA/PGV」开关控制：开关关闭或暂无数据时对应值为 `null`。

```js
const m = await wq.stations.getMetrics()
// {
//   nied:   { pga: 12.3, pgv: 0.85 },  // 日本 NIED：PGA(gal) / PGV(kine)
//   trem:   { pga: 2.52, pgv: 0.58 },  // 台湾 TREM：PGA(gal) / PGV(kine)
//   palert: { pga: 8.1 }               // 台湾 P-Alert：仅 PGA(gal)（该源无 PGV）
// }
if (m.nied.pga != null && m.nied.pga >= 80) wq.ui.toast('NIED PGA 偏高', 'warning')
```

- 单位：PGA = gal，PGV = kine(cm/s)。
- 该接口只读、不订阅；需要持续获取请自行轮询（如每 1~2 秒调用一次）。
- 缺失的键值（如 `palert.pgv`）不会出现，或为 `null`；读取前请判空。

### 6.8 注入数据 —— `wq.data`（需 `data:*`）

把「你自己的数据」以**与真实数据完全相同的路径**注入宿主处理管线（地图叉号、地震波、音效、语音、预警列表、海啸横幅等）。适用于自定义数据源、模拟/演练工具。

```js
wq.data.emitEew({            // 需 data:eew —— EEW 预警
  source: 'cea',             // 机构标识（须为宿主可识别的预警源，见下）
  id: 'evt-123',             // 事件 id：多报请用同一 id，宿主按 id 合并更新
  latitude: 39.9, longitude: 116.4,
  magnitude: 6.2, depth: 10,
  placeName: '模拟震中',
  shockTime: '2026-01-01 12:00:00+08:00', // 发震时刻（含时区标记）
  reportNum: 1,              // 报数（多报递增）；亦可用 updates
  isFinal: false             // 末报置 true；亦可用 final
})
wq.data.emitEarthquake(obj)  // 需 data:earthquake —— 情报/历史（source 为情报源标识时路由到情报管线）
wq.data.emitTsunami(obj)     // 需 data:tsunami —— 海啸预警
```

- **机构标识 `source`**：EEW 预警可用 `cea` / `cea-pr` / `cwa-eew` / `jma` / `kma-eew` / `sa` 等宿主已识别的实时预警源；情报可用 `cwa` / `usgs` / `emsc` / `cenc` 等历史源。未识别的 source 会被宿主忽略。
- **时区**：`jma` / `kma-eew` 按 UTC+9 处理，其余按 UTC+8。时间字符串请带时区标记（如 `+09:00` / `+08:00`），否则宿主按机构默认时区补齐。
- **多报**：同一 `id` 多次 `emitEew`，`reportNum` 递增，宿主会合并为同一事件的连续报。
- **前置条件**：对应预警源需在设置里启用，且 NTP 已校时（未校时会跳过实时预警处理）。

> 参考内置「模拟地震」插件 `com.wquake.simquake` 的实现。

### 6.9 日志 —— `wq.log`

```js
wq.log.info(...args)   // 输出到宿主控制台，带插件 id 前缀
wq.log.warn(...args)
wq.log.error(...args)
```

### 6.10 卸载回调 —— `wq.onUnload`

```js
wq.onUnload(() => { /* 清理定时器、订阅、注入数据 */ })
```

---

## 7. 设置表单 —— settings.schema.json

在 `manifest.json` 声明 `"contributes": { "settingsPage": true }`，并放置 `settings.schema.json`，
宿主会在「设置 → 插件」页自动渲染表单；用户改动通过 `wq.settings.get/getAll/onChange` 读取。

```json
{
  "properties": {
    "minMag": {
      "type": "number", "title": "最小触发震级",
      "description": "低于该震级忽略", "default": 3.0,
      "min": 0, "max": 10, "step": 0.1
    },
    "showToast": { "type": "boolean", "title": "显示 Toast", "default": true },
    "tag": { "type": "string", "title": "自定义标签", "default": "" },
    "level": {
      "type": "enum", "title": "级别", "default": "normal",
      "options": [
        { "label": "低", "value": "low" },
        { "label": "普通", "value": "normal" },
        { "label": "高", "value": "high" }
      ]
    }
  }
}
```

| 字段类型 | 额外属性 |
|----------|----------|
| `number` | `min` / `max` / `step` |
| `boolean` | — |
| `string` | — |
| `enum` | `options: [{ label, value }]` |

通用属性：`title`、`description`、`default`。

---

## 8. 主题插件（CSS 注入）

主题型插件不写脚本，只注入 CSS。`manifest.json` 声明：

```json
"permissions": ["ui:theme", "settings"],
"contributes": { "styles": ["theme.css"], "settingsPage": true }
```

- 启用时宿主把 `theme.css` 注入主文档 `<head>`（仅 `ui:theme` 权限允许）；禁用时自动移除。
- 设置项会作为 `--wqp-*` CSS 变量注入，`theme.css` 通过 `var(--wqp-xxx, 默认值)` 引用，改动即时生效。
- 地图瓦片本身是图片，无法用 CSS 改色；仅能改地图容器底色。

> 浅色/深色文字适配已**下沉到核心**（`app-light-scheme.css` + `html.wq-scheme-light` 类），
> 主题插件只需切换 `--wq-ui-scheme` 与 `--wqp-*` 变量，**无需**逐条覆盖预警/弱反等文字颜色。

---

## 9. 打包与安装

- **内置**：放入 `public/plugins/<id>/`，并在 `public/plugins/index.json` 数组中登记 `id`。
- **上传**：在「设置 → 高级 → 插件管理 → 上传插件」选择一个 `.wqplugin` 文件。
  `.wqplugin` 是一个自包含 JSON：

  ```json
  {
    "manifest": { "...manifest.json 内容..." },
    "files": {
      "index.js": "...源码字符串...",
      "settings.schema.json": "...",
      "theme.css": "..."
    }
  }
  ```

---

## 10. 内置示例插件

| 插件 id | 说明 |
|---------|------|
| `com.wquake.template` | 通用模板：演示事件订阅、提示音、Toast、设置、存储 |
| `com.wquake.station-template` | 测站数据模板：`wq.stations.setBatch` 的震度/烈度用法 |
| `com.wquake.lighttheme` | 浅色主题：`ui:theme` + `contributes.styles` + `--wqp-*` 变量示范 |
